home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / Pascal / OffscreenToys / OffscreenToys 1.3 / OffscreenToys.p < prev    next >
Text File  |  1995-01-11  |  24KB  |  752 lines

  1. {--------- OFFSCREEN TOYS 1.3 ---------}
  2. {by Ingemar Ragnemalm 1994}
  3.  
  4. {An attempt to make a simple, small, stand-alone, compatible offscreen animation demo.}
  5. {I made this since many people want to learn the internals of animation, which packages}
  6. {like SAT (Sprite Animation Toolkit) and aren't good for, even if I had included source.}
  7. {Source code examples should be small!}
  8.  
  9. {Some good points with Offscreen Toys:}
  10. {- It's free, with full source code.}
  11. {- It's complete; no other libraries needed.}
  12. {- It's small, both the source and the compiled binary.}
  13. {- It is a real Mac application with decent event processing.}
  14. {- It works both with and without Color QuickDraw!}
  15. {- Demonstrates the use of fixed-point numbers for speed and positions.}
  16.  
  17. {Some bad points:}
  18. {- It's not designed for re-using for complicated animation projects (which SAT is). The list}
  19. {of sprites is an array with no possibility to handle different kinds of sprites. (That's up to}
  20. {you to add if you want to build upon this.)}
  21. {- It doesn't give the maximum speed possible. You can make QuickDraw draw faster (see}
  22. {the "boosted" version), and you can draw directly to screen.}
  23. {- It is neither MultiFinder-aware nor AppleEvent-aware.}
  24. {- The sprite movement isn't as trivial as it could have been, especially the collision handling}
  25. {but also the fixed-point positions, but hey, I need some fun too! Do we want a version that is}
  26. {strictly integer-based?}
  27. {- A few things that should be const'ed are hard-coded, i.e. the size of the icon (32x32) and}
  28. {the number of fixed-point "binary decimals" (4).}
  29.  
  30. {What can you use it for?}
  31. {- Learn the basics of sprite animation.}
  32. {- Use the glue code for making your program work on non-color Macs. Don't you believe in}
  33. {those old Macs any more? If you make a networked game, you should.}
  34. {- Use as skeleton program.}
  35.  
  36. {Offscreen Toys 1.1 adds better collision handling, making the marbles bounce in a more}
  37. {realistic way.}
  38. {Offscreen Toys 1.2 fixes (?) the bug that sometimes caused a marble to disappear for a}
  39. {few seconds, has better error reporting and works with CodeWarrior. (Original version}
  40. {was Think Pascal only)}
  41. {The "boosted" version uses CopyBits from an offscreen to draw the sprites rather than}
  42. {PlotCIcon. This should speed things up quite a bit.}
  43. {Version 1.3 takes out all reuseable code to a separate file. I hope that will make each part}
  44. {easier to understand and makes the reuseable code easily used from other programs.}
  45.  
  46. program OffscreenToysPlus;
  47.     uses
  48. {$IFC UNDEFINED THINK_PASCAL}
  49.         Types, QuickDraw, Events, Windows, Dialogs, Fonts, DiskInit, TextEdit, Traps, Desk, Memory, SegLoad, Scrap, ToolUtils, OSEvents, OSUtils, Menus, Resources, Packages, {}
  50. {$ENDC}
  51.         QDOffscreen, Sound, OffscreenToysUtils;
  52.  
  53. { --- PART 1: Variables and constants: -----------------------------------------}
  54.  
  55.     const
  56.         kAppleID = 128;
  57.         kFileID = 129;
  58.         kMBarHeight = 20;    { We assume 20 pixels menu bar for window sizing and dragging.}
  59.  
  60.         kWindID = 128;            { Window resource ID }
  61.         kAboutAlertID = 128;    { Alert resource ID }
  62.  
  63.         kSpriteNumber = 5;        { Number of moving objects }
  64.     var
  65.         gWhoa: Boolean;            {True when we want to quit}
  66.         gCollisionFlag: Boolean;    {Collisions or not?}
  67.  
  68. {Menu handles}
  69.         appleMenu, fileMenu: MenuHandle;
  70.  
  71. {The window we'll be using}
  72.         gWind: WindowPtr;
  73.  
  74. {Our two offscreens:}
  75.         offScreen, backScreen: GrafPtr;
  76.  
  77. {A cicn handle}
  78.         gCicn: CIconHandle;
  79.  
  80. {Sprite information. In real games, I prefer making a linked list of records, like I do in}
  81. {SAT, and a lot more information for each, but here we want it *simple*.}
  82. {- position: The positions in local coordinates for the window}
  83. {- fixedPos: 16 times position, which gives us fixed-point numbers}
  84. {- speed: Speed vectors that is added to fixedPos for every frame}
  85. {- r: Rectangles used in drawing, for remembering what part of the screen to update}
  86.         position, fixedPos: array[1..kSpriteNumber] of Point;
  87.         speed: array[1..kSpriteNumber] of Point;
  88.         r: array[1..kSpriteNumber] of Rect;
  89.  
  90. {A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
  91.         gBowlSize: Longint;
  92.  
  93. { --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
  94. {All except Rand moved to OffscreenToysUtils}
  95.  
  96. {Rand: simply make a random number between 0 and range-1.}
  97.  
  98.     function Rand (range: integer): integer;
  99. {var ulrik: integer;}
  100.     begin
  101. {This was the only place where I got bitten by CodeWarrior bugs:}
  102.  
  103. {The following works with MWP:}
  104. {ulrik := Random;}
  105. {ulrik := ulrik mod range;}
  106. {ulrik := abs(ulrik);}
  107. {Rand := ulrik;}
  108.  
  109. {This too:}
  110. {ulrik := Random;}
  111. {Rand := abs(ulrik mod range);}
  112.  
  113. {And, THIS works: !!!}
  114.         Rand := abs(Integer(Random) mod range);
  115.  
  116. {This doesn't:}
  117. {Rand := abs(Random mod range)}
  118.  
  119. {This neither:}
  120. {Rand := BitAnd(Random mod range, $7fff);}
  121.     end;
  122.  
  123. { --- PART 3: Application specific routines: ---------------------------------}
  124.  
  125. {mouse clicks, keydowns, background tasks and update events: This is where all}
  126. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  127. {use this demo as application shell.}
  128.  
  129. {Mouse click in window content}
  130.  
  131.     procedure DoMouse (where: Point; modifiers: Longint);
  132.     begin
  133.     end;
  134.  
  135. {Keydown.}
  136.  
  137.     procedure DoKey (theKey: Char; modifiers: Longint);
  138.     begin
  139.     end;
  140.  
  141. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  142.  
  143. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  144. {the animation. However, it is hard to get high framerate then, since other programs}
  145. {(the Finder included) will process events which will make it less smooth.}
  146.  
  147.     procedure DoBackground;
  148.         const
  149.             kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  150.             kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  151.         var
  152.             tmpRect: Rect;
  153.             i, j: integer;
  154.             vector: Point;
  155.             saveGD: GDHandle;
  156.             savePort: GrafPtr;
  157.             tmpSpeed: Point;
  158.             squaredLength: Longint;
  159.             p1, p2, n1, n2: Point;
  160.  
  161. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  162. {orthogonal to it.}
  163.         procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  164.             var
  165.                 l2, v1pr: Longint;
  166.         begin
  167. {parallell := direction * (v1 DOT direction) /|direction|**2}
  168. {normal := v1 - parallell}
  169.  
  170.             l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  171.             v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  172.  
  173.             parallell.h := direction.h * v1pr div l2;
  174.             parallell.v := direction.v * v1pr div l2;
  175.             normal.h := v1.h - parallell.h;
  176.             normal.v := v1.v - parallell.v;
  177.         end;
  178.  
  179. {A rather boring subroutine that moves the sprites i and j away from each other.}
  180. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  181. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  182. {objects, I'd be happy to put it in.}
  183. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  184. {value as reuseable code - depends on your application.}
  185.         procedure Separate (i, j: integer);
  186.             var
  187.                 initVector, nowVector: Point;
  188.                 absH, absV: integer;
  189.                 moveH, moveV: integer;
  190.                 frac: integer;
  191. {Normal signum function (which I don't think is in the libs)}
  192.             function Sgn (x: integer): integer;
  193.             begin
  194.                 if x > 0 then
  195.                     Sgn := 1
  196.                 else if x < 0 then
  197.                     Sgn := -1
  198.                 else
  199.                     Sgn := 0;
  200.             end;
  201.  
  202.         begin {Separate}
  203.             frac := 0;
  204.             initVector.h := position[i].h - position[j].h;
  205.             initVector.v := position[i].v - position[j].v;
  206.             absH := abs(initVector.h);
  207.             absV := abs(initVector.v);
  208.             moveH := Sgn(initVector.h);
  209.             moveV := Sgn(initVector.v);
  210.             if moveH = 0 then
  211.                 if moveV = 0 then
  212.                     moveV := 1;
  213.             repeat
  214.                 if absH > absV then
  215.                     begin
  216.                         position[i].h := position[i].h + moveH;
  217.                         position[j].h := position[j].h - moveH;
  218.                         frac := frac + absV;
  219.                         if frac > absH then
  220.                             begin
  221.                                 position[i].v := position[i].v + moveV;
  222.                                 position[j].v := position[j].v - moveV;
  223.                                 frac := frac - absH;
  224.                             end
  225.                     end
  226.                 else
  227.                     begin
  228.                         position[i].v := position[i].v + moveV;
  229.                         position[j].v := position[j].v - moveV;
  230.                         frac := frac + absH;
  231.                         if frac > absV then
  232.                             begin
  233.                                 position[i].h := position[i].h + moveH;
  234.                                 position[j].h := position[j].h - moveH;
  235.                                 frac := frac - absV;
  236.                             end
  237.                     end;
  238.                 nowVector.h := position[i].h - position[j].h;
  239.                 nowVector.v := position[i].v - position[j].v;
  240.             until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  241.             fixedPos[i].h := BSL(position[i].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  242.             fixedPos[i].v := BSL(position[i].v, 4);
  243.             fixedPos[j].h := BSL(position[j].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  244.             fixedPos[j].v := BSL(position[j].v, 4);
  245.         end;
  246.  
  247.     begin {DoBackground}
  248.         OTGetGWorld(savePort, saveGD);
  249. {1: Erase all sprites from offScreen}
  250. {Note: We keep the rectangles r[i] for erasing on the screen, later.}
  251.         OTSetGWorld(offScreen, nil);
  252.         for i := 1 to kSpriteNumber do
  253.             begin
  254.                 r[i] := gCicn^^.iconBMap.bounds;
  255.                 OffsetRect(r[i], position[i].h, position[i].v);
  256.                 CopyBits(backScreen^.portBits, offScreen^.portBits, r[i], r[i], srcCopy, nil);
  257.             end;
  258. {2: Change the position and speed}
  259.         for i := 1 to kSpriteNumber do
  260.             begin
  261. {Modify fixed-point position by speed}
  262.                 fixedPos[i].h := fixedPos[i].h + speed[i].h;
  263.                 fixedPos[i].v := fixedPos[i].v + speed[i].v;
  264.  
  265. {Make position by shifting away the 4 binary "decimals"}
  266.                 if fixedPos[i].h >= 0 then
  267.                     position[i].h := BSR(fixedPos[i].h, 4)
  268.                 else
  269.                     position[i].h := BitOr(BSR(fixedPos[i].h, 4), $f000);
  270.                 if fixedPos[i].v >= 0 then
  271.                     position[i].v := BSR(fixedPos[i].v, 4)
  272.                 else
  273.                     position[i].v := BitOr(BSR(fixedPos[i].v, 4), $f000);
  274.  
  275. {Keep inside the window}
  276.                 if fixedPos[i].h + speed[i].h < 0 then
  277.                     speed[i].h := abs(speed[i].h) * kWallBounce div 10 + 1;
  278.                 if fixedPos[i].v + speed[i].v < 0 then
  279.                     speed[i].v := abs(speed[i].v) * kWallBounce div 10 + 1;
  280.                 if position[i].h + gCicn^^.iconBMap.bounds.right > offScreen^.portRect.right then
  281.                     speed[i].h := -abs(speed[i].h) * kWallBounce div 10 - 1;
  282.                 if position[i].v + gCicn^^.iconBMap.bounds.bottom > offScreen^.portRect.bottom then
  283.                     speed[i].v := -abs(speed[i].v) * kWallBounce div 10 - 1;
  284.  
  285. {Are we in the bowl? If we are, accelerate towards the center.}
  286.                 vector.h := position[i].h + 16 - BSR(offScreen^.portRect.right, 1);
  287.                 vector.v := position[i].v + 16 - BSR(offScreen^.portRect.bottom, 1);
  288.                 if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  289.                     begin
  290.                         speed[i].h := speed[i].h - vector.h div 2;
  291.                         speed[i].v := speed[i].v - vector.v div 2;
  292.                     end;
  293.             end; {position/speed loop}
  294.  
  295. {Check for collisions}
  296.         if gCollisionFlag then
  297.             for i := 1 to kSpriteNumber - 1 do {For all objects except the last}
  298.                 for j := i + 1 to kSpriteNumber do {compare its position to all following objects}
  299.                     begin
  300. {Find the vector between them}
  301.                         vector.h := position[i].h - position[j].h;
  302.                         vector.v := position[i].v - position[j].v;
  303.                         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  304. {If it is shorter than the diameter of a ball…}
  305.                         if squaredLength >= 0 then {WHY do I get negative values here? Or is that a CW bug that has been fixed?}
  306.                             if squaredLength < kBallDiameterSquared then
  307.                                 begin
  308. {Move them away from each other}
  309.                                     Separate(i, j);
  310. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  311. {nice and realistic bounces)}
  312.                                     SplitVector(speed[i], vector, p1, n1);
  313.                                     SplitVector(speed[j], vector, p2, n2);
  314.  
  315.                                     speed[j].h := p1.h + n2.h;
  316.                                     speed[j].v := p1.v + n2.v;
  317.  
  318.                                     speed[i].h := p2.h + n1.h;
  319.                                     speed[i].v := p2.v + n1.v;
  320.  
  321. {Old Offscren Toys just swapped the speed, as commented out below. This is not as realistic.}
  322. {tmpSpeed := speed[i];}
  323. {speed[i] := speed[j];}
  324. {speed[j] := tmpSpeed;}
  325.  
  326. {Play a sound. REMOVED since this isn't a sound demo.}
  327. {if gSoundFlag then}
  328. {if SndPlay(nil, GetNamedResource('snd ', 'Kgck'), false) <> noErr then [Ignore error}
  329.  
  330.                                 end;
  331.                     end; {collision loop}
  332.  
  333. {3: Draw sprites in offScreen}
  334. {Note: PlotCIcon (OTPlotCicn) is not very fast! We can speed it up my pre-drawing them in some}
  335. {offscreen, and CopyBits them from there.}
  336.         for i := 1 to kSpriteNumber do
  337.             begin
  338.                 tmpRect := gCicn^^.iconBMap.bounds;
  339.                 OffsetRect(tmpRect, position[i].h, position[i].v);
  340.                 OTPlotCicn(gCicn, offScreen, tmpRect);
  341.             end;
  342.  
  343. {4: Copy sprites to the screen (gWind) - both old and new position!}
  344. {Note: Depending on what limitations we have on movement, we may be able to avoid the multiple}
  345. {CopyBitsing here. E.g. if sprites always move a maximum of 2 pixels, we can copy a 2 pixels}
  346. {larger area, etc.}
  347.         OTSetGWorld(gWind, saveGD); {What GD is most appropriate here?}
  348.         for i := 1 to kSpriteNumber do
  349.             begin
  350.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  351.                 r[i] := gCicn^^.iconBMap.bounds;
  352.                 OffsetRect(r[i], position[i].h, position[i].v);
  353.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  354.             end;
  355.         OTSetGWorld(savePort, saveGD);
  356.     end; {DoBackground}
  357.  
  358. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  359.  
  360. {Note to beginners: A program without update events processing is not a real Mac program!}
  361. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  362. {or worse, partially erased, which is really ugly.}
  363.  
  364.     procedure DoUpdate;
  365.         var
  366.             saveGD: GDHandle;
  367.             savePort: GrafPtr;
  368.     begin
  369.         OTGetGWorld(savePort, saveGD);
  370.         SetPort(gWind); {or OTSetGWorld(gWind, GetMainDevice), in case I forget to et back the device?}
  371.         BeginUpdate(gWind);
  372. {Do drawing here - in this case a CopyBits}
  373.         CopyBits(offScreen^.portBits, gwind^.portBits, gwind^.portRect, gwind^.portRect, srcCopy, nil);
  374.         EndUpdate(gWind);
  375.         OTSetGWorld(savePort, saveGD);
  376.     end;
  377.  
  378. {DoAppleMenu and DoFileMenu: handle menu selections}
  379.  
  380.     procedure DoAppleMenu (item: integer);
  381.         var
  382.             str: Str255;
  383.             h: Handle;
  384.             saveGD: GDHandle;
  385.             savePort: GrafPtr;
  386.             ignore: integer;
  387.     begin
  388.         if item = 1 then
  389.             begin
  390.                 if Alert(kAboutAlertID, nil) = 1 then
  391.                     ; {Ignore result}
  392.             end
  393.         else
  394. {Apple menu other than "About": Code from TransSkel}
  395.             begin
  396.                 OTGetGWorld(savePort, saveGD); {I guess GetPort would be ok}
  397.                 GetItem(appleMenu, item, str);
  398.                 SetResLoad(false);
  399.                 h := GetNamedResource('DRVR', str);
  400.                 SetResLoad(true);
  401.                 if h <> nil then
  402.                     begin
  403.                         ResrvMem(SizeResource(h) + $1000);
  404.                         ignore := OpenDeskAcc(str);
  405.                     end;
  406.                 OTSetGWorld(savePort, saveGD);
  407.             end;
  408.     end; {DoAppleMenu}
  409.  
  410.     procedure DoFileMenu (item: integer);
  411.         var
  412.             start, finish, frames: Longint;
  413.             fpsStr: Str255;
  414.     begin
  415.         case item of
  416.             1:
  417. {Run animation without event processing until the user clicks the mouse}
  418. {Note: This runs the animation at maximum speed. In real programs, we}
  419. {must limit the speed with the system clock, e.g. inspect TickCount.}
  420.                 begin
  421.                     start := TickCount;
  422.                     frames := 0;
  423.                     while not Button do
  424.                         begin
  425.                             DoBackground;
  426.                             frames := frames + 1;
  427.                         end;
  428.                     finish := TickCount;
  429.                     NumToString(frames * 60 div (finish - start), fpsStr);
  430.                     ParamText(fpsStr, ' frames/second', '', '');
  431.                     if Alert(129, nil) = 1 then
  432.                         ;
  433.                 end;
  434.             2: 
  435.                 begin
  436.                     gCollisionFlag := not gCollisionFlag;
  437.                     CheckItem(fileMenu, 2, gCollisionFlag);
  438.                 end;
  439. {Set the flag that tells the program to quit.}
  440.             4: 
  441.                 gWhoa := true;
  442.         end; {case}
  443.     end; {DoFileMenu}
  444.  
  445. { --- PART 4: Event processing: -----------------------------------------}
  446.  
  447. {MenuSelection: Menu selection by mouse or command-key:}
  448.  
  449.     procedure MenuSelection (whatSelection: longInt);
  450.     begin
  451.         case HiWord(whatSelection) of
  452.             kAppleID: 
  453.                 DoAppleMenu(LoWord(whatSelection));
  454.             kFileID: 
  455.                 DoFileMenu(LoWord(whatSelection));
  456.         end; {case}
  457.         HiLiteMenu(0);
  458.     end;
  459.  
  460. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  461. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  462.  
  463.     procedure MainLoop;
  464.         const
  465.             kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
  466.         var
  467.             hasEvent: Boolean;
  468.             theEvent: EventRecord;
  469.             theKey: Char;
  470.             whatSelection: Longint;
  471.             whichPart: integer;
  472.             whichWindow: WindowPtr;
  473.             r: rect;
  474.     begin
  475. {Get the next event. Use WaitNextEvent if possible.}
  476.         if gHasWNE then
  477.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  478.         else
  479.             begin
  480.                 SystemTask;
  481.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  482.             end;
  483.  
  484. {OK, so what happened then?}
  485.         if hasEvent then
  486.             case theEvent.what of
  487.                 mouseDown: 
  488.                     begin
  489.                         whichPart := FindWindow(theEvent.where, whichWindow);
  490.                         case whichPart of
  491.                             inMenuBar: 
  492.                                 begin
  493.                                     whatSelection := MenuSelect(theEvent.where);
  494.                                     MenuSelection(whatSelection);
  495.                                 end;
  496.                             inSysWindow: 
  497.                                 SystemClick(theEvent, whichWindow);
  498.                             inGoAway: 
  499.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  500.                                     gWhoa := true;
  501.                             inDrag: 
  502.                                 begin
  503.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  504.                                         SelectWindow(whichWindow);
  505. {$IFC UNDEFINED THINK_PASCAL}
  506.                                     r := qd.screenBits.bounds;            {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  507. {$ELSEC}
  508.                                     r := screenBits.bounds;            {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  509. {$ENDC}
  510.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  511.                                     InsetRect(r, 4, 4);
  512.                                     DragWindow(whichWindow, theEvent.where, r);
  513.                                 end;
  514.                             inGrow: 
  515.                                 ;  {Ignored - we don't resize}
  516.                             inContent: 
  517.                                 if (whichWindow <> FrontWindow) then
  518.                                     SelectWindow(whichWindow)
  519.                                 else
  520.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  521.                         end; {case whichPart}
  522.                     end; {mouseDown}
  523.                 keyDown, autoKey: 
  524.                     begin
  525.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  526.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  527.                             MenuSelection(MenuKey(theKey))
  528.                         else
  529.                             DoKey(theKey, theEvent.modifiers);
  530.                     end;
  531.                 updateEvt:
  532. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  533.                     if WindowPtr(theEvent.message) = gWind then
  534.                         DoUpdate;
  535. {Handle disk inserts like TransSkel.}
  536.                 diskEvt: 
  537.                     if (HiWord(theEvent.message) <> noErr) then
  538.                         begin
  539.                             DILoad;
  540.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  541.                                 ;
  542.                             DIUnload;
  543.                         end; {diskEvt}
  544.                 otherwise {Other events are ignored}
  545.             end; {case}
  546.  
  547.         DoBackground;
  548.     end;
  549.  
  550. { --- PART 5: Initializations: -----------------------------------------}
  551.  
  552. {OTInit: Initialize global flags, menus and window}
  553.  
  554.     procedure OTInit;
  555.         const
  556. {Trap numbers}
  557.             _WaitNextEvent = $A860;
  558.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  559.             k32bQD = $AB1D;
  560.             _SndPlay = $A805;
  561.         var
  562.             appleStr: Str255;
  563.     begin
  564.  
  565. {In case this isn't Think Pascal we have to make the standard inits ourselves.}
  566. {$IFC UNDEFINED THINK_PASCAL}
  567.         InitGraf(@qd.thePort);
  568.         InitFonts;
  569.         InitWindows;
  570.         InitMenus;
  571.         TEInit;
  572.         InitDialogs(nil);
  573. {InitCursor;}
  574.         MaxApplZone;
  575. {$ENDC}
  576.  
  577. {Over to more interesting stuff}
  578.  
  579.         OTInitGlobals;            {Init OffscreenToysUtils}
  580.  
  581.         gWhoa := false;
  582.         gCollisionFlag := false;
  583.  
  584. {We could check with Gestalt instead, but that isn't necessary here since we aren't using any}
  585. {optional services (like QuickTime).}
  586.  
  587. {$IFC UNDEFINED THINK_PASCAL}
  588.         qd.randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  589. {$ELSEC}
  590.         randSeed := TickCount;                {Seed the random number generator - TickCount is good enough.}
  591. {$ENDC}
  592.  
  593. {Get the window, a color window if we are going to use color.}
  594.         if gColorQDFlag then
  595.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  596.         else
  597.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  598.  
  599. {Some menus. We could read these from resources.}
  600.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  601.         AppendMenu(appleMenu, 'About OffscreenToys…;(-');
  602.         AddResMenu(appleMenu, 'DRVR');
  603.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  604.         fileMenu := NewMenu(kFileID, 'File');
  605.         AppendMenu(fileMenu, 'Try max speed;Collisions;(-;Quit/Q');
  606.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  607.         DrawMenuBar;
  608.     end;
  609.  
  610. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  611.  
  612.     procedure OTOffscreensInit;
  613.         var
  614.             saveGD: GDHandle;
  615.             savePort: GrafPtr;
  616.             thePat: PixPatHandle;
  617.             r: Rect;
  618.             i: integer;
  619.             colorFlag: Boolean;
  620.         const
  621.             patID = 128;
  622.  
  623. {A little routine for setting the forecolor with a single line.}
  624.         procedure OTForeColor (red, green, blue: integer);
  625.             var
  626.                 theColor: RGBColor;
  627.         begin
  628.             theColor.red := red;
  629.             theColor.green := green;
  630.             theColor.blue := blue;
  631.             RGBForeColor(theColor);
  632.         end;
  633.  
  634.     begin {OTOffscreensInit}
  635.         OTGetGWorld(savePort, saveGD);
  636.  
  637.         OTNewGWorld(offScreen, gWind^.portRect);
  638.         OTNewGWorld(backScreen, gWind^.portRect);
  639.  
  640.         OTSetGWorld(backScreen, nil);
  641.  
  642. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  643.  
  644. {For drawing the background, let's make a local flag that tells us if we shold draw}
  645. {b/w patterns or color ones.}
  646.         if gColorQDFlag then
  647.             colorFlag := (CGrafPtr(backScreen)^.portPixMap^^.pixelSize > 1)
  648.         else
  649.             colorFlag := false;
  650.  
  651.         if colorFlag then
  652.             begin
  653.                 thePat := GetPixPat(patID);
  654.                 PenPixPat(thePat)
  655.             end
  656.         else
  657.             begin
  658.                 thePat := PixPatHandle(GetResource('ppat', patID));
  659.                 PenPat(thePat^^.pat1Data);
  660.             end;
  661.         PaintRect(backScreen^.portRect);
  662.         PenNormal;
  663.  
  664. {Then we draw some circles.}
  665.  
  666.         r := backScreen^.portRect;
  667.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  668.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  669.         if colorFlag then
  670.             begin
  671.                 OTForeColor(-10000, -10000, -10000);
  672.                 PaintOval(r);
  673.             end
  674.         else
  675. {$IFC UNDEFINED THINK_PASCAL}
  676.             FillOval(r, qd.ltGray);
  677. {$ELSEC}
  678.         FillOval(r, ltGray);
  679. {$ENDC}
  680.  
  681.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  682.         if colorFlag then
  683.             begin
  684.                 OTForeColor(-25000, -25000, -25000);
  685.                 PaintOval(r);
  686.             end
  687.         else
  688. {$IFC UNDEFINED THINK_PASCAL}
  689.             FillOval(r, qd.gray);
  690. {$ELSEC}
  691.         FillOval(r, gray);
  692. {$ENDC}
  693.  
  694.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  695.         if colorFlag then
  696.             begin
  697.                 OTForeColor(20000, 20000, 20000);
  698.                 PaintOval(r);
  699.             end
  700.         else
  701. {$IFC UNDEFINED THINK_PASCAL}
  702.             FillOval(r, qd.dkGray);
  703. {$ELSEC}
  704.         FillOval(r, dkGray);
  705. {$ENDC}
  706.  
  707.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  708.         if colorFlag then
  709.             OTForeColor(0, 0, 0);
  710.         PaintOval(r);
  711.  
  712. {Done drawing!}
  713. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  714. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  715.  
  716.         OTSetGWorld(offScreen, nil);
  717.         CopyBits(backScreen^.portBits, offScreen^.portBits, backScreen^.portRect, backScreen^.portRect, srcCopy, nil);
  718.  
  719.         OTSetGWorld(savePort, saveGD);
  720.  
  721. {Get the cicn resource}
  722. {Note: You can, of course, use several cicns and switch between.}
  723.         gCicn := OTGetCicn(128);
  724.  
  725. {Initialize the sprite information arrays:}
  726.  
  727.         for i := 1 to kSpriteNumber do
  728.             begin
  729.                 position[i].h := Rand(offScreen^.portRect.right - 32);
  730.                 position[i].v := (i - 1) * (offScreen^.portRect.bottom - 32) div 5 + Rand((offScreen^.portRect.bottom - 32) div 5);
  731.                 fixedPos[i].h := BSL(position[i].h, 4);
  732.                 fixedPos[i].v := BSL(position[i].v, 4);
  733.                 speed[i].h := Random mod 32;
  734.                 speed[i].v := Random mod 32;
  735.             end;
  736.     end;
  737.  
  738. { --- MAIN PROGRAM BODY: -----------------------------------------}
  739.  
  740. begin
  741.     OTInit;                    {General initializations}
  742.     OTOffscreensInit;        {Set up the offscreen grafports}
  743.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  744.  
  745. {Run until quit or click in the close box.}
  746.     repeat
  747.         MainLoop;
  748.     until gWhoa;
  749.  
  750. {No cleanup is necessary here.}
  751. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  752. end.